package ru.amse.tsyganov.jumleditor.view.common;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;

import ru.amse.tsyganov.jumleditor.graphicaleditor.Coordinates;
import ru.amse.tsyganov.jumleditor.model.common.Edge;
import ru.amse.tsyganov.jumleditor.model.common.GraphModel;
import ru.amse.tsyganov.jumleditor.view.ConnectionPoint;
import ru.amse.tsyganov.jumleditor.view.ViewVisitor;
import ru.amse.tsyganov.jumleditor.view.activepoints.ActivePoint;
import ru.amse.tsyganov.jumleditor.view.activepoints.LineEditingActivePoint;
import ru.amse.tsyganov.jumleditor.view.lines.Line;
import ru.amse.tsyganov.jumleditor.view.lines.LineBuilder;
import ru.amse.tsyganov.jumleditor.view.lines.LinePainter;
import ru.amse.tsyganov.jumleditor.view.preferences.Colors;

public abstract class EdgeView<T extends Edge> extends ViewElement<T> {
	
	private final GraphView<? extends GraphModel> graphView;
	
	private final List<ActivePoint> points;
	
	private Point stringExtent;
	
	private ConnectionPoint sourceConnectionPoint;
	
	private ConnectionPoint targetConnectionPoint;
	
	private Line[] brokenLine;
	
	public EdgeView(T model,
			GraphView<? extends GraphModel> activityGraphView,
			ConnectionPoint sourceConnectionPoint,
			ConnectionPoint targetConnectionPoint) {
		
		super(model);
		
		if (activityGraphView == null || sourceConnectionPoint == null || 
				targetConnectionPoint == null) {
			
			throw new IllegalArgumentException();
		}
		
		points = createActivePoints();
		this.graphView = activityGraphView;
		this.sourceConnectionPoint = sourceConnectionPoint;
		this.targetConnectionPoint = targetConnectionPoint;
		initBrokenLine();
	}
	
	@Override
	public void paint(GC gc, Coordinates coordinates) {
		assert gc != null;
		
		gc.setForeground(getValidity().getColor());
		gc.setBackground(getValidity().getColor());
		
		LinePainter.drawArrowedLine(gc, coordinates, brokenLine);
				
		drawString(gc, coordinates);
	}

	@Override
	public void prepaintEvent(GC gc, Coordinates coordinates) {
		stringExtent = gc.stringExtent(getText());
	}

	public void drawString(GC gc, Coordinates coordinates) {
		gc.setBackground(Colors.getWhiteColor());
		gc.drawString(getText(), 
				coordinates.fromModelXToScreenX(
						getTextOutputPoint().x - stringExtent.x / 2), 
				coordinates.fromModelYToScreenY(
						getTextOutputPoint().y - stringExtent.y / 2));
	}
	
	public abstract String getText();

	@Override
	public void accept(ViewVisitor v) {
		v.visitEdgeView(this);
	}
	
	@Override
	public List<? extends ActivePoint> getActivePoints() {
		if (getSelectedItems().selectedTransitionsContains(this)) {
			return points;
		} else {
			return Collections.emptyList();
		}
	}
	
	@Override
	public GraphView<? extends GraphModel> getActivityGraphView() {
		return graphView;
	}
	
	public Point getStartPoint() {
		return new Point(sourceConnectionPoint.getCpX(),
				sourceConnectionPoint.getCpY());
	}
	
	public Point getEndPoint() {
		return new Point(targetConnectionPoint.getCpX(),
				targetConnectionPoint.getCpY());
	}
	
	public Point getTextOutputPoint() {
		return new Point(
				(brokenLine[0].getX1() + brokenLine[0].getX2()) / 2,
				(brokenLine[0].getY1() + brokenLine[0].getY2()) / 2);
	}
	
	public abstract List<ActivePoint> createActivePoints();

	@Override
	public void paintActivePoints(GC gc, Coordinates coordinates) {
		if (getSelectedItems().selectedTransitionsContains(this)) {
			for (ActivePoint p : getActivePoints()) {
				p.paintActivePoint(gc, coordinates);
			}
		}
	}
	
	public Point getStringExtent() {
		return stringExtent != null ? 
				stringExtent :
					new Point(10, 10);
	}

	public ConnectionPoint getSourceConnectionPoint() {
		return sourceConnectionPoint;
	}

	public void setSourceConnectionPoint(ConnectionPoint sourceConnectionPoint) {
		if (sourceConnectionPoint == null) {
			throw new IllegalArgumentException();
		}
		this.sourceConnectionPoint = sourceConnectionPoint;
		initBrokenLine();
	}

	public ConnectionPoint getTargetConnectionPoint() {
		return targetConnectionPoint;
	}

	public void setTargetConnectionPoint(ConnectionPoint targetConnectionPoint) {
		if (targetConnectionPoint == null) {
			throw new IllegalArgumentException();
		}
		this.targetConnectionPoint = targetConnectionPoint;
		initBrokenLine();
	}
	
	public boolean contains(int x, int y) {
		if (brokenLine == null) {
			return false;
		}
		final int D = 10; 
		for (Line l : brokenLine) {
			int minX = Math.min(l.getX1(), l.getX2()) - D;
			int minY = Math.min(l.getY1(), l.getY2()) - D;
			int maxX = Math.max(l.getX1(), l.getX2()) + D;
			int maxY = Math.max(l.getY1(), l.getY2()) + D;
			if (new Rectangle(minX, minY, maxX - minX, maxY - minY).contains(x, y)) {
				return true;
			}
		}
		return false;
	}
	
	public void initBrokenLine() {
		for (Iterator<ActivePoint> i = points.iterator(); i.hasNext(); ) {
			ActivePoint p = i.next();
			if (p instanceof LineEditingActivePoint) {
				i.remove();
			}
		}

		brokenLine = LineBuilder.getBrokenLine(sourceConnectionPoint, targetConnectionPoint);
		for (int i = 1; i < brokenLine.length - 1; i++) {
			LineEditingActivePoint p = new LineEditingActivePoint(brokenLine[i]);
			points.add(p);
		}
	}
	
	public void initBrokenLine(int v, int h) {
		for (Iterator<ActivePoint> i = points.iterator(); i.hasNext(); ) {
			ActivePoint p = i.next();
			if (p instanceof LineEditingActivePoint) {
				i.remove();
			}
		}

		brokenLine = LineBuilder.getBrokenLine(sourceConnectionPoint, targetConnectionPoint, v, h);
		for (int i = 1; i < brokenLine.length - 1; i++) {
			LineEditingActivePoint p = new LineEditingActivePoint(brokenLine[i]);
			points.add(p);
		}
	}
	
	public int getBrokenLineVerticalSegment() {
		if (brokenLine[0].getY1() == brokenLine[0].getY2()) {
			return brokenLine[0].getX2();
		} else {
			return brokenLine[brokenLine.length - 1].getX1();
		}
	}
	
	public int getBrokenLineHorizontalSegment() {
		if (brokenLine[0].getX1() == brokenLine[0].getX2()) {
			return brokenLine[0].getY2();
		} else {
			return brokenLine[brokenLine.length - 1].getY1();
		}
	}

	public Line[] getBrokenLine() {
		return brokenLine;
	}
}
